package com.qyer.search.searchplan;

import static com.qyer.search.enums.LogicalOperator.AND;
import static com.qyer.search.enums.LogicalOperator.OR;
import static com.qyer.search.enums.MatchType.MATCH;
import static com.qyer.search.enums.MatchType.MATCH_PHRASE;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import org.elasticsearch.index.query.NestedQueryBuilder;

import com.qyer.search.Constants;
import com.qyer.search.cache.mutable.MutableCaches;
import com.qyer.search.enums.LogicalOperator;
import com.qyer.search.enums.MatchType;
import com.qyer.search.exception.SearchException;
import com.qyer.search.model.SearchParameter;
import com.qyer.words.model.Word;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;

import java.util.Iterator;
import java.util.List;
import java.util.UUID;

/**
 * User: Z J Wu Date: 2016/01/25 Time: 23:07 Package: com.qyer.search.model
 */
public class SQPlan {

  public enum SearchJobType {
    QUERY, SEARCH, QUERY_SEARCH
  }

  private final String id = UUID.randomUUID().toString();

  private final int PST_TYPE1 = 0;
  private final int PST_TYPE2 = 1;
  private final int PST_COUNTRY_ID = 2;
  private final int PST_CITY_ID = 3;
  private final int PST_SOURCE_PLACE_ID = 4;
  private final int PST_SUPPLIER_ID = 5;
  private final int PST_DEST = 6;
  private final int PST_WORDS = 7;

  private SearchJobType type;

  // 一组没有歧义的关键词. 词与词的关系可以是and也可以是or
  private List<Word> words;

  // 最终的QueryBuilder, 可能是search, query, query-search
  private QueryBuilder queryBuilder;
  private QueryBuilder conditionQueryBuilder;

  // SearchParameter包含筛选条件
  private SearchParameter searchParameter;

  private byte conditions = 0;
  private byte condition_byte = 127;

  public SQPlan(SearchParameter sp) throws SearchException {
    this(sp, null);
  }

  /*public SQPlan(SearchParameter sp, List<Word> words) throws SearchException {
    this.words = words;
    this.searchParameter = sp;
    conditions = (byte) (conditions | (((sp.getType1() > 0) ? 1 : 0) << PST_TYPE1));
    conditions = (byte) (conditions | (((sp.getType2() > 0) ? 1 : 0) << PST_TYPE2));
    int countryId = sp.getDestinationCountryId(), cityId = sp.getDestinationCityId();
    conditions = (byte) (conditions | (((countryId > 0) ? 1 : 0) << PST_COUNTRY_ID));
    conditions = (byte) (conditions | (((cityId > 0) ? 1 : 0) << PST_CITY_ID));
    conditions = (byte) (conditions | (((sp.getSetOutPlaceId() > 0) ? 1 : 0) << PST_SOURCE_PLACE_ID));
    conditions = (byte) (conditions | (((sp.getSupplierId() > 0) ? 1 : 0) << PST_SUPPLIER_ID));
    conditions = (byte) (conditions | (((CollectionUtils.isEmpty(words)) ? 0 : 1) << PST_WORDS));
    conditions = (byte) (conditions | ((StringUtils.isBlank(sp.getDestination()) ? 0 : 1) << PST_DEST));
    this.type = checkParseType();
    if (hasCondition()) {
      this.conditionQueryBuilder = buildQueryCondition();
    }
    this.queryBuilder = isQuery() ? this.conditionQueryBuilder : buildSearch();
  }*/

  public SQPlan(SearchParameter sp, List<Word> words) throws SearchException {
    this.words = words;
    this.searchParameter = sp;
    conditions = (byte) (conditions | (((sp.getType1() > 0) ? 1 : 0) << PST_TYPE1));
    conditions = (byte) (conditions | (((sp.getType2() > 0) ? 1 : 0) << PST_TYPE2));
    int countryId = sp.getDestinationCountryId(), cityId = sp.getDestinationCityId();
    conditions = (byte) (conditions | (((countryId > 0) ? 1 : 0) << PST_COUNTRY_ID));
    conditions = (byte) (conditions | (((cityId > 0) ? 1 : 0) << PST_CITY_ID));
    conditions = (byte) (conditions | (((sp.getSetOutPlaceId() > 0) ? 1 : 0) << PST_SOURCE_PLACE_ID));
    conditions = (byte) (conditions | (((sp.getSupplierId() > 0) ? 1 : 0) << PST_SUPPLIER_ID));
    conditions = (byte) (conditions | (((CollectionUtils.isEmpty(words)) ? 0 : 1) << PST_WORDS));
    conditions = (byte) (conditions | ((StringUtils.isBlank(sp.getDestination()) ? 0 : 1) << PST_DEST));
    conditions = (byte) (conditions | ((StringUtils.isBlank(sp.getDestCountry()) ? 0 : 1) << PST_DEST));
    this.type = checkParseType();
    if (hasCondition()) {
      this.conditionQueryBuilder = buildQueryConditionEx();
    }
    this.queryBuilder = isQuery() ? this.conditionQueryBuilder : buildSearch();
  }

  public SearchJobType checkParseType() throws SearchException {
    if (hasCondition() && hasKW()) {
      return SearchJobType.QUERY_SEARCH;
    } else if (hasCondition() && !hasKW()) {
      return SearchJobType.QUERY;
    } else if (!hasCondition() && hasKW()) {
      return SearchJobType.SEARCH;
    } else {
      throw new SearchException("Conditions and keywords are null.");
    }
  }

  public boolean isQuery() {
    return this.conditions > 0;
  }

  public boolean hasKW() {
    return this.conditions < 0;
  }

  // fixme 写死了, 有待改进, 因为新加入条件还需要修改此方法
  public boolean hasCondition() {
    //return (this.conditions & ((1 << 4) - 1)) > 0;
    return (this.conditions & this.condition_byte) > 0;
  }

  public boolean hasCondition(int conditionPosition) {
    return (this.conditions & (1 << conditionPosition)) > 0;
  }

  public SearchJobType getType() {
    return type;
  }

  public String getId() {
    return id;
  }

  public List<Word> getWords() {
    return words;
  }

  public QueryBuilder getQueryBuilder() {
    return queryBuilder;
  }

  public SearchParameter getSearchParameter() {
    return searchParameter;
  }

  public byte getConditions() {
    return conditions;
  }

  /**
   * 构建单个bool条件匹配
   *
   * @param left  已知的BoolQueryBuilder
   * @param lo    逻辑操作符
   * @param mt    匹配类型(单字匹配, 单词匹配)
   * @param field 作用域
   * @param val   值
   * @throws SearchException
   */
  public static void appendCondition(BoolQueryBuilder left, LogicalOperator lo, MatchType mt,
                                     String field, Object val) {
    QueryBuilder right;
    switch (lo) {
      case AND:
        switch (mt) {
          case MATCH:
            right = matchQuery(field, val);
            left.must(right);
            return;
          case MATCH_PHRASE:
            right = matchPhraseQuery(field, val);
            left.must(right);
            return;
        }
      case OR:
        switch (mt) {
          case MATCH:
            right = matchQuery(field, val);
            left.should(right);
            return;
          case MATCH_PHRASE:
            right = matchPhraseQuery(field, val);
            left.should(right);
            return;
        }
      default:
        throw new UnsupportedOperationException("Unsupported logical operator");
    }
  }

  /**
   * 条件字段之间的关系都是合取. 包括1级类目, 2级类目, 目的地国家/城市
   *
   * @return
   * @throws SearchException
   */
  public QueryBuilder buildQueryCondition() throws SearchException{
    BoolQueryBuilder bqb = boolQuery();
    /************************************************/
    QueryBuilder nestedQueryBuilder = null;
    /************************************************/
    if (hasCondition(PST_TYPE1)) {
      appendCondition(bqb, AND, MATCH, "cid", searchParameter.getType1());
    }
    if (hasCondition(PST_TYPE2)) {
      appendCondition(bqb, AND, MATCH, "ccid", searchParameter.getType2());
    }
    if (hasCondition(PST_COUNTRY_ID)) {
      appendCondition(bqb, AND, MATCH, "countries.id", searchParameter.getDestinationCountryId());
    }
    if (hasCondition(PST_CITY_ID)) {
      appendCondition(bqb, AND, MATCH, "cities.id", searchParameter.getDestinationCityId());
    }
    if (hasCondition(PST_SOURCE_PLACE_ID)) {
      appendCondition(bqb, AND, MATCH, "set_out_place.id", searchParameter.getSetOutPlaceId());
    }
    if (hasCondition(PST_SUPPLIER_ID)) {
      appendCondition(bqb, AND, MATCH, "supplierid", searchParameter.getSupplierId());
    }
    if (hasCondition(PST_DEST)) {
      BoolQueryBuilder bqb_dest = boolQuery();
      String dest = searchParameter.getDestination();
      if(MutableCaches.getInstance().OVERSEA_CITIES.getCacheContent().containsKey(dest)){
        //appendCondition(bqb, AND, MATCH, "tags.tag_name", dest + "9");
        //System.out.println("城市目的地："+dest+"9");
        /***********************************************************/
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_name", dest);
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_type", 9);
        nestedQueryBuilder = new NestedQueryBuilder("tags", bqb_dest);
        /**********************************************************/
      }else if(MutableCaches.getInstance().COUNTRIES.getCacheContent().containsKey(dest)){
        //appendCondition(bqb, AND, MATCH, "tags.tag_name", dest + "8");
        //System.out.println("国家目的地：" + dest + "8");
        /**********************************************************/
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_name", dest);
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_type", 8);
        nestedQueryBuilder = new NestedQueryBuilder("tags", bqb_dest);
        /*********************************************************/
      }else if(Constants.CONTINENT_MAP.containsKey(dest)){
        //appendCondition(bqb, AND, MATCH, "tags.tag_name", dest + "10");
        //System.out.println("大洲目的地：" + dest + "10");
        /**********************************************************/
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_name", dest);
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_type", 10);
        nestedQueryBuilder = new NestedQueryBuilder("tags", bqb_dest);
        /**********************************************************/
      }else {
        throw new SearchException("destination is incorrect,which is "+dest);
      }
    }
    /*************************test***************************/
    if(nestedQueryBuilder != null){
      return bqb.must(nestedQueryBuilder);
    }
    /*******************************************************/
    return bqb;
  }

  public QueryBuilder buildQueryConditionEx() throws SearchException{
    //System.out.println("buildQueryConditionEx："+System.currentTimeMillis());
    BoolQueryBuilder bqb = boolQuery();
    /************************************************/
    QueryBuilder nestedQueryBuilder = null;
    QueryBuilder nestedQueryBuilderCountry = null;
    /************************************************/
    if (hasCondition(PST_TYPE1)) {
      appendCondition(bqb, AND, MATCH, "cid", searchParameter.getType1());
    }
    if (hasCondition(PST_TYPE2)) {
      appendCondition(bqb, AND, MATCH, "ccid", searchParameter.getType2());
    }
    if (hasCondition(PST_COUNTRY_ID)) {
      appendCondition(bqb, AND, MATCH, "countries.id", searchParameter.getDestinationCountryId());
    }
    if (hasCondition(PST_CITY_ID)) {
      appendCondition(bqb, AND, MATCH, "cities.id", searchParameter.getDestinationCityId());
    }
    if (hasCondition(PST_SOURCE_PLACE_ID)) {
      appendCondition(bqb, AND, MATCH, "set_out_place.id", searchParameter.getSetOutPlaceId());
    }
    if (hasCondition(PST_SUPPLIER_ID)) {
      appendCondition(bqb, AND, MATCH, "supplierid", searchParameter.getSupplierId());
    }
    if (hasCondition(PST_DEST)) {
      BoolQueryBuilder bqb_dest = boolQuery();
      BoolQueryBuilder bqb_destCountry = boolQuery();
      String dest = searchParameter.getDestination();
      String destCountry = searchParameter.getDestCountry();
      if(dest != null){
        //System.out.println("plan城市:" + dest + "t1:" + searchParameter.getType1() + " t2:"+ searchParameter.getType2());
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_name", dest);
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_type", 9);
        nestedQueryBuilder = new NestedQueryBuilder("tags", bqb_dest);
        bqb.must(nestedQueryBuilder);
      }
      if(destCountry != null){
        //System.out.println("plan国家:" + destCountry + "t1:" + searchParameter.getType1() + " t2:"+ searchParameter.getType2());
        appendCondition(bqb_destCountry, AND, MATCH, "tags.tag_name", destCountry);
        appendCondition(bqb_destCountry, AND, MATCH, "tags.tag_type", 8);
        nestedQueryBuilderCountry = new NestedQueryBuilder("tags", bqb_destCountry);
        bqb.must(nestedQueryBuilderCountry);
      }
    }
    /*************************test***************************/
    /*******************************************************/
    return bqb;
  }

  public QueryBuilder buildFilterQueryCondition() throws SearchException{
    //System.out.println("buildQueryConditionEx："+System.currentTimeMillis());
    BoolQueryBuilder bqb = boolQuery();
    /************************************************/
    QueryBuilder nestedQueryBuilder = null;
    QueryBuilder nestedQueryBuilderCountry = null;
    /************************************************/
    if (hasCondition(PST_TYPE1)) {
      appendCondition(bqb, AND, MATCH, "cid", searchParameter.getType1());
    }
    if (hasCondition(PST_TYPE2)) {
      appendCondition(bqb, AND, MATCH, "ccid", searchParameter.getType2());
    }
    if (hasCondition(PST_COUNTRY_ID)) {
      appendCondition(bqb, AND, MATCH, "countries.id", searchParameter.getDestinationCountryId());
    }
    if (hasCondition(PST_CITY_ID)) {
      appendCondition(bqb, AND, MATCH, "cities.id", searchParameter.getDestinationCityId());
    }
    if (hasCondition(PST_SOURCE_PLACE_ID)) {
      appendCondition(bqb, AND, MATCH, "set_out_place.id", searchParameter.getSetOutPlaceId());
    }
    if (hasCondition(PST_SUPPLIER_ID)) {
      appendCondition(bqb, AND, MATCH, "supplierid", searchParameter.getSupplierId());
    }
    if (hasCondition(PST_DEST)) {
      BoolQueryBuilder bqb_dest = boolQuery();
      BoolQueryBuilder bqb_destCountry = boolQuery();
      String dest = searchParameter.getDestination();
      String destCountry = searchParameter.getDestCountry();
      if(dest != null){
        //System.out.println("plan城市:" + dest + "t1:" + searchParameter.getType1() + " t2:"+ searchParameter.getType2());
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_name", dest);
        appendCondition(bqb_dest, AND, MATCH, "tags.tag_type", 9);
        nestedQueryBuilder = new NestedQueryBuilder("tags", bqb_dest);
        bqb.must(nestedQueryBuilder);
      }
      if(destCountry != null){
        //System.out.println("plan国家:" + destCountry + "t1:" + searchParameter.getType1() + " t2:"+ searchParameter.getType2());
        appendCondition(bqb_destCountry, AND, MATCH, "tags.tag_name", destCountry);
        appendCondition(bqb_destCountry, AND, MATCH, "tags.tag_type", 8);
        nestedQueryBuilderCountry = new NestedQueryBuilder("tags", bqb_destCountry);
        bqb.must(nestedQueryBuilderCountry);
      }
    }
    /*************************test***************************/
    /*******************************************************/
    return bqb;
  }

  public QueryBuilder appendQueryCondition(QueryBuilder searchQueryBuilder) {
    return boolQuery().must(searchQueryBuilder).must(conditionQueryBuilder);
  }

  /**
   * 命题: 1级类目匹配=p, 2级类目匹配=q, 目的地国家匹配=r, 目的地城市匹配=s 标题匹配=x, 标签匹配=y
   * <p/>
   * 则, 条件匹配 m = p ∧ q ∧ r ∧ s, 关键词匹配n=x ∨ y
   * <p/>
   * 则搜索匹配 = m ∧ n, 即条件字段内部是合取, 关键词字段内部是析取, 条件字段与关键词字段是合取
   *
   * @param kwStr
   * @return
   */
  public QueryBuilder buildSingleWordSearch(String kwStr) {
    BoolQueryBuilder kwQB = boolQuery();
    appendCondition(kwQB, OR, MATCH_PHRASE, "title", kwStr);
    appendCondition(kwQB, OR, MATCH_PHRASE, "tags.tag_name", kwStr);
    if("818大促".equals(kwStr)){
      appendCondition(kwQB, OR, MATCH_PHRASE, "z_tags.tag_name", kwStr);
    }
    return hasCondition() ? appendQueryCondition(kwQB) : kwQB;
  }

  public QueryBuilder buildSearch() {
    QueryBuilder qb;
    Iterator<Word> it = words.iterator();
    if (words.size() == 1) {
      qb = buildSingleWordSearch(it.next().getName());
    } else {
      BoolQueryBuilder bqb = boolQuery();
      while (it.hasNext()) {
        bqb.must(buildSingleWordSearch(it.next().getName()));
      }
      qb = bqb;
    }
    return qb;
  }

}
